package com.jfire.orm.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import com.jfire.baseutil.collection.StringCache;
import com.jfire.baseutil.reflect.ReflectUtil;
import com.jfire.baseutil.verify.Verify;
import com.jfire.orm.metadata.MetaData;

public class DynamicSqlTool
{
    /**
     * 分析动态sql语句，并且生成动态sql情况下的前置的热编码代码部分
     * 
     * @param sql
     * @param paramNames
     * @param paramTypes
     * @return
     * @throws NoSuchFieldException
     * @throws SecurityException
     */
    public static String analyseDynamicSql(String sql, String[] paramNames, Class<?>[] paramTypes) throws NoSuchFieldException, SecurityException
    {
        sql = transMapSql(sql);
        String context = "StringBuilder builder = new StringBuilder();List list = new ArrayList();";
        int pre = 0;
        int now = 0;
        int quote = 0;
        boolean split = false;
        String section = null;
        while (now < sql.length())
        {
            switch (sql.charAt(now))
            {
                case '\'':
                    // 如果不是转义字符
                    if (sql.charAt(now - 1) != '\\')
                    {
                        // 如果是右单引号，则代表完结
                        quote = quote == 0 ? 1 : 0;
                    }
                    now++;
                    break;
                case '[':
                    if (quote == 1)
                    {
                        now++;
                        break;
                    }
                    section = sql.substring(pre, now);
                    context += "builder.append(\"" + section + "\");";
                    pre = now + 1;
                    now++;
                    break;
                case ']':
                    if (quote == 1)
                    {
                        now++;
                        break;
                    }
                    section = sql.substring(pre, now).trim();
                    context += "if(" + createVarIf(section, paramNames, paramTypes) + "){";
                    pre = now + 1;
                    now++;
                    break;
                case '{':
                    if (quote == 1)
                    {
                        now++;
                        break;
                    }
                    if (sql.charAt(now - 1) == '%')
                    {
                        section = sql.substring(pre, now - 1);
                        context += "builder.append(\"" + section + "\").append('?');";
                        context += "list.add('%'+";
                    }
                    else if (sql.charAt(now - 1) == '~')
                    {
                        section = sql.substring(pre, now - 1);
                        context += "builder.append(\"" + section + "\").append(\" (\");";
                        split = true;
                    }
                    else
                    {
                        section = sql.substring(pre, now);
                        context += "builder.append(\"" + section + "\").append('?');";
                        context += "list.add(";
                    }
                    pre = now + 1;
                    now++;
                    break;
                case '}':
                    if (quote == 1)
                    {
                        now++;
                        break;
                    }
                    if (split == false)
                    {
                        section = sql.substring(pre, now);
                        context += "($w)" + buildParam(section, paramNames, paramTypes, sql);
                        if (now < sql.length() - 1)
                        {
                            if (sql.charAt(now + 1) == '%')
                            {
                                context += "+'%');";
                                now += 2;
                                pre = now;
                            }
                            else
                            {
                                context += ");";
                                now++;
                                pre = now;
                            }
                        }
                        else
                        {
                            context += ");";
                            return context;
                        }
                        break;
                    }
                    else
                    {
                        split = false;
                        section = sql.substring(pre, now);
                        Class<?> paramType = getParamType(section, paramNames, paramTypes, sql);
                        if (paramType.equals(String.class))
                        {
                            context += "{String[] tmp = ((String)" + buildParam(section, paramNames, paramTypes, sql) + ").split(\",\");";
                        }
                        else if (paramType.equals(String[].class))
                        {
                            context += "{String[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(int[].class))
                        {
                            context += "{int[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(Integer[].class))
                        {
                            context += "{Integer[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(long[].class))
                        {
                            context += "{long[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(Long[].class))
                        {
                            context += "{Long[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(float[].class))
                        {
                            context += "{float[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(Float[].class))
                        {
                            context += "{Float[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(double[].class))
                        {
                            context += "{double[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (paramType.equals(Double[].class))
                        {
                            context += "{Double[] tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else if (List.class.isAssignableFrom(paramType))
                        {
                            context += "{java.util.List tmp = " + buildParam(section, paramNames, paramTypes, sql) + ";";
                        }
                        else
                        {
                            throw new RuntimeException("in操作中存在不识别的类型");
                        }
                        if (List.class.isAssignableFrom(paramType))
                        {
                            context += "int length = tmp.size()-1;";
                        }
                        else
                        {
                            context += "int length = tmp.length-1;";
                        }
                        context += "for(int i=0;i<length;i++){builder.append(\"?,\");}";
                        context += "builder.append(\"?)\");";
                        context += "length++;";
                        if (List.class.isAssignableFrom(paramType))
                        {
                            context += "for(int i=0;i<length;i++){list.add(($w)tmp.get(i));}";
                        }
                        else
                        {
                            context += "for(int i=0;i<length;i++){list.add(($w)tmp[i]);}";
                        }
                        context += "}";
                        now++;
                        pre = now;
                        break;
                    }
                case '#':
                    if (quote == 1)
                    {
                        now++;
                        break;
                    }
                    section = sql.substring(pre, now);
                    context += "builder.append(\"" + section + "\");}";
                    pre = now + 1;
                    now++;
                    break;
                default:
                    now++;
                    break;
            }
        }
        section = sql.substring(pre, now);
        context += "builder.append(\"" + section + "\");";
        return context;
    }
    
    /**
     * 给定参数字符串inject，在所有的方法入参名称中搜索可能的对应值。
     * 比如字符串为user.name。有一个参数为类user,并且参数位置在第一个。则返回的内容是$1.getName()
     * 如果只是单一参数，比如name。有一个参数为name。并且在第一个。返回的内容是$1
     * 
     * @param inject
     * @param paramNames
     * @param paramTypes
     * @param originalSql
     * @return
     * @throws NoSuchFieldException
     * @throws SecurityException
     */
    public static String buildParam(String inject, String[] paramNames, Class<?>[] paramTypes, String originalSql) throws NoSuchFieldException, SecurityException
    {
        if (inject.indexOf('.') == -1)
        {
            int index = getParamNameIndex(inject, paramNames);
            return "$" + (index + 1);
        }
        else
        {
            String[] tmp = inject.split("\\.");
            int index = getParamNameIndex(tmp[0], paramNames);
            String getMethodName = ReflectUtil.buildGetMethod(inject, paramTypes[index]);
            return "$" + (index + 1) + getMethodName;
        }
    }
    
    /**
     * 给定字符串inject，搜索可能的参数字符串。比如字符串为user.name，有一个参数为类user。
     * 则参数字符串应该是user.getName()
     * 返回的结果是这个方法或者这个参数的类型。如果方法的返回类型是数组，则返回的结果是这个数组的元素类型
     * 
     * @param inject
     * @param paramNames
     * @param paramTypes
     * @param originalSql
     * @return
     * @throws NoSuchFieldException
     * @throws SecurityException
     */
    private static Class<?> getParamType(String inject, String[] paramNames, Class<?>[] paramTypes, String originalSql) throws NoSuchFieldException, SecurityException
    {
        if (inject.indexOf('.') == -1)
        {
            Integer index = getParamNameIndex(inject, paramNames);
            Verify.notNull(index, "sql注入语句{}无法找到注入属性{}", originalSql, inject);
            return paramTypes[index];
        }
        else
        {
            String[] tmp = inject.split("\\.");
            Integer index = getParamNameIndex(tmp[0], paramNames);
            Verify.notNull(index, "sql注入语句{}无法找到注入属性{}", originalSql, inject);
            return ReflectUtil.getFinalReturnType(inject, paramTypes[index]);
        }
    }
    
    /**
     * 通过字符比对，确定需要注入的属性是第几个参数的内部属性或者内容
     * 
     * @param inject
     * @param paramNames
     * @return
     */
    public static int getParamNameIndex(String inject, String[] paramNames)
    {
        for (int i = 0; i < paramNames.length; i++)
        {
            if (paramNames[i].equals(inject))
            {
                return i;
            }
        }
        throw new RuntimeException("给定的参数" + inject + "不在参数列表中");
    }
    
    /**
     * 将sql语句中的类映射和字段映射替换为各自映射的数据库表名和字段名
     * 
     * @param sql
     * @return
     */
    public static String transMapSql(String sql)
    {
        try
        {
            String rootSimpleClassName = null;
            if (sql.contains("@"))
            {
                int end = 0;
                int index = 0;
                do
                {
                    index = sql.indexOf('@', end);
                    if (index == -1)
                    {
                        break;
                    }
                    if ((end = sql.indexOf(' ', index)) == -1 && (end = sql.indexOf('=', index)) == -1 && (end = sql.indexOf(',', index)) == -1 && (end = sql.indexOf(')', index)) == -1 && (end = sql.indexOf(']', index)) == -1)
                    {
                        end = sql.length();
                    }
                    String simpleClassName = sql.substring(index + 1, end);
                    Verify.notNull(MapBeanFactory.getMetaData(simpleClassName), "sql:{}存在错误，类{}没有被注解MapTabel或TableEntity标记。", sql, simpleClassName);
                    rootSimpleClassName = simpleClassName;
                } while (true);
                Verify.notNull(rootSimpleClassName, "sql有问题，没有指明映射类，请检查是否类前面缺少了@，请检查{}", sql);
                StringCache cache = new StringCache();
                int length = sql.length();
                index = 0;
                int state = 0;// 0代表没有东西，1代表之前发现一个@，2代表发现一个&
                int flag = 0;
                boolean ignore = false;
                while (index < length)
                {
                    char c = sql.charAt(index);
                    switch (c)
                    {
                        case '\'':
                            ignore = !ignore;
                            cache.append(c);
                            break;
                        case '@':
                            if (ignore)
                            {
                                cache.append(c);
                                break;
                            }
                            Verify.True(state == 0, "sql语句有问题，请查看{}", sql);
                            state = 1;
                            flag = index + 1;
                            break;
                        case '^':
                            if (ignore)
                            {
                                cache.append(c);
                                break;
                            }
                            Verify.True(state == 0, "sql语句有问题，请查看{}", sql);
                            state = 2;
                            flag = index + 1;
                            break;
                        case ' ':
                        case '<':
                        case '>':
                        case '!':
                        case ',':
                        case '=':
                        case ')':
                        case ']':
                        {
                            if (ignore)
                            {
                                cache.append(c);
                                break;
                            }
                            if (state == 1)
                            {
                                String entityClassSimpleName = sql.substring(flag, index);
                                String replaceName = MapBeanFactory.getMetaData(entityClassSimpleName).getTableName();
                                cache.append(replaceName);
                                state = 0;
                            }
                            else if (state == 2)
                            {
                                String context = sql.substring(flag, index);
                                if (context.indexOf('.') != -1)
                                {
                                    String[] tmp = context.split("\\.");
                                    Verify.True(tmp.length == 2, "sql有错误，请检查{},关注：{}", sql, context);
                                    MetaData metaData = MapBeanFactory.getMetaData(tmp[0]);
                                    Verify.notNull(metaData, "sql存在错误，请检查{},类{}不存在映射，关注{}", sql, tmp[0], context);
                                    cache.append(metaData.getTableName()).append('.').append(metaData.getColumnName(tmp[1]));
                                }
                                else
                                {
                                    MetaData metaData = MapBeanFactory.getMetaData(rootSimpleClassName);
                                    Verify.notNull(metaData.getColumnName(context), "sql存在错误,字段{}不存在，请检查{}", context, sql);
                                    cache.append(metaData.getColumnName(context));
                                }
                                state = 0;
                            }
                            cache.append(c);
                            break;
                        }
                        default:
                            if (state == 0)
                            {
                                cache.append(c);
                            }
                            break;
                    }
                    index++;
                }
                if (state == 1)
                {
                    String entityClassSimpleName = sql.substring(flag, index);
                    String replaceName = MapBeanFactory.getMetaData(entityClassSimpleName).getTableName();
                    cache.append(replaceName);
                }
                else if (state == 2)
                {
                    String context = sql.substring(flag, index);
                    if (context.indexOf('.') != -1)
                    {
                        String[] tmp = context.split("\\.");
                        Verify.True(tmp.length == 2, "sql有错误，请检查{},关注：{}", sql, context);
                        MetaData metaData = MapBeanFactory.getMetaData(tmp[0]);
                        Verify.notNull(metaData, "sql存在错误，请检查{},类{}不存在映射，关注{}", sql, tmp[0], context);
                        cache.append(metaData.getTableName()).append('.').append(metaData.getColumnName(tmp[1]));
                    }
                    else
                    {
                        MetaData metaData = MapBeanFactory.getMetaData(rootSimpleClassName);
                        Verify.notNull(metaData.getColumnName(context), "sql存在错误,字段{}不存在，请检查{}", context, sql);
                        cache.append(metaData.getColumnName(context));
                    }
                }
                return cache.toString();
            }
            else
            {
                return sql;
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 分析格式化的sql语句，根据格式化语句和方法形参名称数组得出标准sql语句，和对应的object[]形的参数数组
     * 
     * @param originalSql
     * @param paramNames
     * @return
     * @throws SecurityException
     * @throws NoSuchFieldException
     */
    public static String[] analyseFormatSql(String originalSql, String[] paramNames, Class<?>[] paramTypes) throws NoSuchFieldException, SecurityException
    {
        List<String> variateNames = new ArrayList<>();
        String formatSql = getFormatSql(originalSql, variateNames);
        String param = buildParams(formatSql, variateNames.toArray(new String[0]), paramNames, paramTypes);
        return new String[] { formatSql, param };
    }
    
    /**
     * 将给定的sql语句转换为格式化的sql语句。将其中的{变量名}替换为?。并且将{}中的内容增加到paramNames中
     * 返回格式化后的sql语句
     * 
     * @param sql
     * @param paramNames
     * @return
     */
    public static String getFormatSql(String sql, List<String> variateNames)
    {
        sql = transMapSql(sql);
        StringCache formatSql = new StringCache();
        int length = sql.length();
        char c;
        int index = 0;
        boolean inVariate = false;
        int variateStart = 0;
        while (index < length)
        {
            c = sql.charAt(index);
            switch (c)
            {
                case '{':
                    Verify.False(inVariate, "sql存在错误，尖括号不能嵌套。请检查sql：{}，并关注{}", sql, sql.substring(0, variateStart));
                    inVariate = true;
                    variateStart = index + 1;
                    break;
                case '}':
                    Verify.True(inVariate, "sql存在错误，尖括号没有闭合。请检查sql：{}，并关注{}", sql, sql.substring(0, variateStart));
                    inVariate = false;
                    formatSql.append('?');
                    variateNames.add(sql.substring(variateStart, index));
                    break;
                default:
                    if (inVariate == false)
                    {
                        formatSql.append(c);
                    }
                    break;
            }
            index++;
        }
        return formatSql.toString();
    }
    
    /**
     * 根据格式化sql中的注入字段，和方法形参名称数组，返回Object[]形式的参数数组
     * 
     * @param originalSql
     * @param length
     * @param injects
     * @param paramNames
     * @return
     * @throws SecurityException
     * @throws NoSuchFieldException
     */
    private static String buildParams(String originalSql, String[] injects, String[] paramNames, Class<?>[] paramTypes) throws NoSuchFieldException, SecurityException
    {
        int length = injects.length;
        if (length == 0)
        {
            return "new Object[0]";
        }
        String[] params = new String[length];
        for (int i = 0; i < length; i++)
        {
            String inject = injects[i];
            params[i] = DynamicSqlTool.buildParam(inject, paramNames, paramTypes, originalSql);
        }
        StringCache cache = new StringCache();
        cache.append("new Object[]{");
        for (int i = 0; i < length; i++)
        {
            cache.append("($w)").append(params[i]).appendComma();
        }
        if (cache.isCommaLast())
        {
            cache.deleteLast();
        }
        cache.append("}");
        return cache.toString();
    }
    
    /**
     * 检查是否是动态sql
     * 
     * @param sql
     * @return
     */
    public static boolean isDynamic(String sql)
    {
        Stack<Character> stack = new Stack<>();
        int now = 0;
        int quote = 0;
        boolean dynamic = false;
        while (now < sql.length())
        {
            char c = sql.charAt(now);
            switch (c)
            {
                case '\'':
                    quote = quote == 0 ? 1 : 0;
                    break;
                case '[':
                    if (quote != 1)
                    {
                        stack.push(c);
                    }
                    break;
                case ']':
                    if (quote != -1)
                    {
                        Verify.True(stack.peek() == '[', "sql语句存在问题，缺少[。请检查{},并且关注", sql, sql.subSequence(0, now));
                        stack.push(c);
                    }
                    break;
                case '#':
                    if (quote != 1)
                    {
                        Verify.True(stack.pop() == ']', "sql语句存在问题，缺少]。请检查{},并且关注", sql, sql.subSequence(0, now));
                        Verify.True(stack.pop() == '[', "sql语句存在问题，缺少[。请检查{},并且关注", sql, sql.subSequence(0, now));
                        dynamic = true;
                    }
                    break;
                case '~':
                    if (quote != 1)
                    {
                        Verify.True(now < sql.length() - 1, "sql语句存在错误，符号~不应该在最后一个，请检查{}", sql);
                        Verify.True(sql.charAt(now + 1) == '{', "sql语句存在错误，符号~后面应该紧跟{。请检查{}，并关注{}", sql, sql.substring(0, now));
                        dynamic = true;
                    }
                    break;
                default:
                    break;
            }
            now++;
        }
        return dynamic;
    }
    
    public static String createVarIf(String sql, String[] paramNames, Class<?>[] types) throws NoSuchFieldException, SecurityException
    {
        StringCache cache = new StringCache();
        char c;
        int flag = 0;
        // sql中字符串的开始读取位置
        int strRead = 0;
        // 变量名开始读取的位置。-1表示没有
        int varStart = -1;
        // 条件字段开始读取的位置。-1表示灭有
        int conditionStart = -1;
        // 参数字段开始读取的位置。-1表示没有
        int paramStart = -1;
        // 参数
        String condition = null;
        // 变量的类型
        Class<?> varType = null;
        // 变量名经过转换后的内容。比如user.name转换后可能是$1.getName()
        String transVar = null;
        // 单引号开始位置。0表示没有
        int quote = 0;
        while (flag < sql.length())
        {
            c = sql.charAt(flag);
            if (c == '\'')
            {
                if (conditionStart != -1)
                {
                    condition = sql.substring(conditionStart, flag);
                    conditionStart = -1;
                }
                if (quote == 0)
                {
                    cache.append(sql.substring(strRead, flag));
                    quote = 1;
                    paramStart = flag + 1;
                }
                else
                {
                    handParam(sql.substring(paramStart, flag), cache, transVar, varType, condition);
                    strRead = flag + 1;
                    quote = 0;
                    paramStart = -1;
                }
            }
            else if (c == '$')
            {
                varStart = flag;
                cache.append(sql.substring(strRead, flag)).append(" ( ");
            }
            else if (c == '>' || c == '<' || c == '!' || c == '=')
            {
                if (varStart != -1)
                {
                    String var = sql.substring(varStart + 1, flag);
                    transVar = buildParam(var, paramNames, types, sql);
                    varType = getParamType(var, paramNames, types, sql);
                    varStart = -1;
                }
                if (conditionStart == -1)
                {
                    conditionStart = flag;
                }
                strRead = flag + 1;
            }
            else if (c == ' ')
            {
                if (varStart != -1)
                {
                    String var = sql.substring(varStart + 1, flag);
                    transVar = buildParam(var, paramNames, types, sql);
                    varType = getParamType(var, paramNames, types, sql);
                    varStart = -1;
                }
                else if (conditionStart != -1)
                {
                    condition = sql.substring(conditionStart, flag);
                    conditionStart = -1;
                }
                else if (paramStart != -1)
                {
                    Verify.notNull(transVar, "sql语句存在异常，条件判断中没有变量，请检查是否缺少了$。条件判断为{},并关注{}", sql, sql.substring(0, flag));
                    Verify.notNull(condition, "sql语句存在异常，条件判断中没有体检。条件判断为{},并关注{}", sql, sql.substring(0, flag));
                    handParam(sql.substring(paramStart, flag), cache, transVar, varType, condition);
                    transVar = null;
                    varType = null;
                    condition = null;
                    paramStart = -1;
                }
                else
                {
                    cache.append(sql.substring(strRead, flag + 1));
                }
                strRead = flag;
            }
            else if (c == '(' || c == ')' || c == '&' || c == '|')
            {
                cache.append(sql.substring(strRead, flag + 1));
                strRead = flag + 1;
            }
            else
            {
                if (conditionStart != -1)
                {
                    condition = sql.substring(conditionStart, flag);
                    conditionStart = -1;
                    paramStart = flag;
                }
                if (quote == 0 && varStart == -1 && paramStart == -1)
                {
                    paramStart = flag;
                }
            }
            flag++;
        }
        if (varStart != -1)
        {
            String var = sql.substring(varStart + 1);
            transVar = buildParam(var, paramNames, types, sql);
            varType = getParamType(var, paramNames, types, sql);
            int index = 0;
            while ((index = transVar.indexOf('.', index)) != -1)
            {
                cache.append(transVar.substring(0, index)).append(" != null && ");
                index++;
            }
            if (varType.isPrimitive() == false)
            {
                cache.append(transVar).append(" != null )");
            }
            else{
                cache.deleteEnds(3).append(" )");
            }
        }
        if (paramStart != -1)
        {
            Verify.notNull(transVar, "sql语句存在异常，条件判断中没有变量，请检查是否缺少了$。条件判断为{},并关注{}", sql, sql.substring(0, flag));
            Verify.notNull(condition, "sql语句存在异常，条件判断中没有体检。条件判断为{},并关注{}", sql, sql.substring(0, flag));
            handParam(sql.substring(paramStart), cache, transVar, varType, condition);
        }
        return cache.toString();
    }
    
    private static void handParam(String param, StringCache formatSql, String transVar, Class<?> varType, String condition)
    {
        // 如果是user.name，需要判断user！=null 并且user.getName() !=
        // null。必须逐层验证
        int flag = 0;
        while ((flag = transVar.indexOf('.', flag)) != -1)
        {
            formatSql.append("($w)").append(transVar.substring(0, flag)).append(" != null && ");
            flag++;
        }
        if (varType.isPrimitive() == false)
        {
            formatSql.append(transVar).append(" != null && ");
        }
        if (varType.isPrimitive())
        {
            formatSql.append(transVar).append(condition).append(param).append(" )");
        }
        else if (varType == String.class)
        {
            if (condition.equals("=="))
            {
                formatSql.append(transVar).append(".equals(\"").append(param).append("\") )");
            }
            else if (condition.equals("!="))
            {
                formatSql.append(transVar).append(".equals(\"").append(param).append("\")==false )");
            }
        }
        else
        {
            if (varType == Integer.class)
            {
                formatSql.append(transVar).append(".intValue() ").append(condition).append(param).append(" )");
            }
            else if (varType == Long.class)
            {
                formatSql.append(transVar).append(".longValue() ").append(condition).append(param).append(" )");
            }
            else if (varType == Short.class)
            {
                formatSql.append(transVar).append(".shortValue() ").append(condition).append(param).append(" )");
            }
            else if (varType == Double.class)
            {
                formatSql.append(transVar).append(".doubleValue() ").append(condition).append(param).append(" )");
            }
            else if (varType == Float.class)
            {
                formatSql.append(transVar).append(".floatValue() ").append(condition).append(param).append(" )");
            }
            else if (varType == Long.class)
            {
                formatSql.append(transVar).append(".longValue() ").append(condition).append(param).append(" )");
            }
            else
            {
                throw new RuntimeException("不能识别的处理类型" + varType);
            }
        }
        
    }
}
